using System;
using System.Collections;

using Evaluant.OPath;
using Evaluant.OPath.Expressions;
using Evaluant.Uss.Common;

namespace Evaluant.Uss
{
	public class Navigator
	{
		public Navigator()
		{
		}

		public Navigator(Entity root) : this(new EntitySet(new Entity[]{ root }))
		{
			if(root == null)
				throw new ArgumentNullException("root");
		}

		public Navigator(EntitySet roots)
		{
			if(roots == null)
				throw new ArgumentNullException("roots");

			_Roots = roots;
		}

		private EntitySet _Roots = new EntitySet();
		public EntitySet Roots
		{
			get { return _Roots; }
		}
 
		public object[] SelectElements(string opath)
		{
			OPathQuery query = new OPathQuery(opath);
			query.Compile();
			
			if(query.HasErrors)
				throw new OPathException(query);

			return SelectElements(query.Path);
		}

		public object[] SelectElements(Path path)
		{
			if(_Roots.Count == 0)
				return new object[0];

			ArrayList _Result = new ArrayList(_Roots);

			foreach(Identifier ident in path.Identifiers)
			{
				// Clones the collection
				ArrayList topLevel = new ArrayList(_Result);
				_Result = new ArrayList();

				foreach(object rootElement in topLevel)
				{
					// Can only navigate in an Entity
					if(!(rootElement is Entity))
						continue;

					EntitySet childSet;
					if(ident.Value == "this")
					{
						// "this" represents the current node
						childSet = new EntitySet();
						childSet.Add((Entity)rootElement);
					}
					else // Try to get the sub item as an Entity
						childSet = ((Entity)rootElement).GetEntitySet(ident.Value);

					if(childSet.Count > 0)
					{
						foreach(Entity child in childSet)
						{
							if(ident.Constraint != null) // keep this entity if the constraint is verified
							{
                                NavigationVisitor checker = new NavigationVisitor();
								checker._Roots.Clear();
								checker._Roots.Add(child);
								ident.Constraint.Accept(checker);
								object result = checker._NodeValue;
								if(result != null && (result is bool) && (bool)result && !_Result.Contains(child))
									_Result.Add(child);
							}
							else
								if(!_Result.Contains(child))
									_Result.Add(child);
						}
					}
					else
					{
						object child = null;
						
						if(ident.Value == "Id")
							child = ((Entity)rootElement).Id;
						else
							child = ((Entity)rootElement).GetValue(ident.Value);

						if(child == null) // Go to the next item if the specified identifier was not found
							continue;

						_Result.Add(child);
					}
				}
			}

			return _Result.ToArray();
		}


		/// <summary>
		/// Evaluates an opath expression.
		/// </summary>
		/// <param name="expression">Expression.</param>
		/// <returns></returns>
		public object Evaluate(string expression)
		{

			string opath = String.Format("this[{0}]", expression);

			OPathQuery query = new OPathQuery(opath);
			query.Compile();
			
			if(query.HasErrors)
				throw new OPathException(query);

			return Evaluate(query.Path);
		}

		/// <summary>
		/// Evaluates on opath expression on a specific object.
		/// </summary>
		/// <param name="expression">Expression.</param>
		/// <param name="root">Root.</param>
		/// <returns></returns>
		public object Evaluate(string expression, Entity root)
		{
			string opath = String.Format("this[{0}]", expression);

			OPathQuery query = new OPathQuery(opath);
			query.Compile();
			
			if(query.HasErrors)
				throw new OPathException(query);

			Roots.Clear();
			Roots.Add(root);

			return Evaluate(query.Path);
		}

		internal object Evaluate(Path path)
		{
			Identifier ident = path.Identifiers[0];

			if(ident.Constraint != null) // keep this entity if the constraint is verified
			{
				NavigationVisitor checker = new NavigationVisitor();
				checker._Roots.Clear();
				checker._Roots.AddRange(_Roots);
				ident.Constraint.Accept(checker);
				if( ((object[])checker._NodeValue).Length > 0)
					return ((object[])checker._NodeValue)[0];
				else
					return null;
			}

			return null;
		}
	}
}
